﻿using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

namespace Projeto__A_Cura
{

    /// <summary>
    /// Classe gerenciadora dos estados / cenas do jogo.
    /// </summary>
    class SceneManager
    {
        /// <summary>
        /// Lista preenchida automaticamente com todos os estados / cenas do jogo.
        /// </summary>
        public static List<BaseScene> scene_list = new List<BaseScene>();

        /// <summary>
        /// Enumeração com todos os estados / cenas do jogo. Prenchimento manual.
        /// </summary>
        public enum Scenes { 
            SCENE00,
            SCENE01,
            SCENE02,
            SCENE03,
            SCENE04,
            SCENE05,
            SCENE06,
            SCENE07,
            SCENE08,
            SCENE09, 
        }

        /// <summary>
        /// Controla o estado / cena atual em que o jogo está.
        /// </summary>
        public static Scenes actualScene = Scenes.SCENE01;

        /// <summary>
        /// Guarda o estado / cena anterior em que o jogo esteve.
        /// </summary>
        public static Scenes lastScene = Scenes.SCENE00;

        /// <summary>
        /// Usado para mostrar informações durante o desenvolvimento do jogo.
        /// </summary>
        public static bool DEBUG = false;

        /// <summary>
        /// Fonte usada durante os testes do jogo para mostrar informações.
        /// </summary>
        public static SpriteFont arial;

        /// <summary>
        /// Contrutor da classe SceneManager. TO DO Fazer singleton?
        /// </summary>
        public SceneManager() { }

        /// <summary>
        /// Chamado uma fez em Game1 para o objeto sceneManager, carrega todas as cenas / áudios.
        /// </summary>
        /// <param name="Content">Carregador do jogo</param>
        public void LoadContent(ContentManager Content)
        {
            //iterar pela lista de cenas e carregar tudo
            foreach (BaseScene scene in scene_list)
            {
                scene.LoadContent(Content);
            }

            //carregamento da fonte estática
            arial = Content.Load<SpriteFont>("arial");
        }

        /// <summary>
        /// Chamado uma fez em Game1 para o objeto sceneManager, atualiza todas as cenas.
        /// </summary>
        /// <param name="keyboard">Teclado no frame atual</param>
        /// <param name="old_keyboard">Teclado no frame anterior</param>
        /// <param name="gameTime">Tempo do jogo</param>
        public void Update(KeyboardState keyboard, KeyboardState old_keyboard, GameTime gameTime)
        {
            //iterar pela lista de cenas e atualizar tudo
            foreach (BaseScene scene in scene_list)
            {
                scene.Update(keyboard, old_keyboard, gameTime, actualScene);
                //scene.Update(gameTime, actualScene);

                if (SceneManager.lastScene != SceneManager.actualScene)
                {
                   break;//test();
                }
                //else { continue; }
            }
        }

        public void test() { }

        /// <summary>
        /// Chamado uma fez em Game1 para o objeto sceneManager, desenha informações de todas as cenas em modo DEBUG. 
        /// </summary>
        /// <param name="gameTime">Tempo do jogo</param>
        /// <param name="spriteBatch">Desenhador do jogo</param>
        public void Draw(GameTime gameTime, SpriteBatch spriteBatch, KeyboardState keyboard, KeyboardState old_keyboard)
        {
            //só mostra as informações se estiver em testes...
            if (DEBUG)
            {
                //iterar pela lista de cenas e desenhar tudo
                foreach (BaseScene scene in scene_list)
                {
                    scene.Draw(gameTime, spriteBatch, actualScene, keyboard, old_keyboard);
                }
            }
        } 
    }

    /// <summary>
    /// Classe base abstrata para as cenas / estados, todos tem que derivar dela, não cria objetos diretamente.
    /// </summary>
    abstract class BaseScene
    {
        protected Song audio01;

        protected string contentDir = "Audio - A Cura\\Audio Demo\\";

        protected bool wasAudio01Played = false;//to do ver game1 reiniciar da fase 1

        protected SceneManager.Scenes thisScene;

        protected string description = "";

        //protected KeyboardState keyboard;

        //protected KeyboardState old_keyboard;
        
        public BaseScene(SceneManager.Scenes _thisScene)
        {
            thisScene = _thisScene;
            SceneManager.scene_list.Add(this);
        }

        public abstract void LoadContent(ContentManager Content);

        public abstract void Update(
            KeyboardState keyboard,
            KeyboardState old_keyboard,
            GameTime gameTime,
            SceneManager.Scenes actualScene);

        //public abstract void Update(
        //    GameTime gameTime,
        //    SceneManager.Scenes actualScene);

        public void Draw(
            GameTime gameTime,
            SpriteBatch spriteBatch,
            SceneManager.Scenes actualScene,
            KeyboardState keyboard,
            KeyboardState old_keyboard)
        {
            if (actualScene == thisScene)
            {
                spriteBatch.DrawString(SceneManager.arial, "Descrição: "+description, Vector2.Zero, Color.Red);
                spriteBatch.DrawString(SceneManager.arial, "Estado/cena atual: " + thisScene.ToString()+SceneManager.actualScene.ToString(), new Vector2(0, SceneManager.arial.MeasureString("A").Y*1), Color.Red);
                spriteBatch.DrawString(SceneManager.arial, "Estado/cena anterior: " + SceneManager.lastScene.ToString(), new Vector2(0, SceneManager.arial.MeasureString("A").Y*2), Color.Red);
                spriteBatch.DrawString(SceneManager.arial, "Teclado atual: " + keyboard.GetHashCode(), new Vector2(0, SceneManager.arial.MeasureString("A").Y*3), Color.Red);
                spriteBatch.DrawString(SceneManager.arial, "Teclado anterior: " + old_keyboard.GetHashCode(), new Vector2(0, SceneManager.arial.MeasureString("A").Y * 4), Color.Red);
                spriteBatch.DrawString(SceneManager.arial, "Nome do áudio: "+audio01.Name, new Vector2(0, SceneManager.arial.MeasureString("A").Y*5), Color.Red);
            }
        }

        //bool conferir se está na cena, return

        //tocar a primeira vez...

        //voltar para o anterior...
    }
}
























/*
 * using System;

public class Singleton
{
   private static Singleton instance;

   private Singleton() {}

   public static Singleton Instance
   {
      get 
      {
         if (instance == null)
         {
            instance = new Singleton();
         }
         return instance;
      }
   }
}

 * public sealed class Singleton
{
   private static readonly Singleton instance = new Singleton();
   
   private Singleton(){}

   public static Singleton Instance
   {
      get 
      {
         return instance; 
      }
   }
}

 * 
 * // Bad code! Do not use!
public sealed class Singleton
{
    static Singleton instance=null;

    Singleton()
    {
    }

    public static Singleton Instance
    {
        get
        {
            if (instance==null)
            {
                instance = new Singleton();
            }
            return instance;
        }
    }
}


 * 
 * Second version - simple thread-safety
public sealed class Singleton
{
    static Singleton instance=null;
    static readonly object padlock = new object();

    Singleton()
    {
    }

    public static Singleton Instance
    {
        get
        {
            lock (padlock)
            {
                if (instance==null)
                {
                    instance = new Singleton();
                }
                return instance;
            }
        }
    }
}

 

 * Third version - attempted thread-safety using double-check locking
// Bad code! Do not use!
public sealed class Singleton
{
    static Singleton instance=null;
    static readonly object padlock = new object();

    Singleton()
    {
    }

    public static Singleton Instance
    {
        get
        {
            if (instance==null)
            {
                lock (padlock)
                {
                    if (instance==null)
                    {
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }
}

 

 * 
 * Fourth version - not quite as lazy, but thread-safe without using locks
public sealed class Singleton
{
    static readonly Singleton instance=new Singleton();

    // Explicit static constructor to tell C# compiler
    // not to mark type as beforefieldinit
    static Singleton()
    {
    }

    Singleton()
    {
    }

    public static Singleton Instance
    {
        get
        {
            return instance;
        }
    }
}

 

 * 
 * Fifth version - fully lazy instantiation
public sealed class Singleton
{
    Singleton()
    {
    }

    public static Singleton Instance
    {
        get
        {
            return Nested.instance;
        }
    }
    
    class Nested
    {
        // Explicit static constructor to tell C# compiler
        // not to mark type as beforefieldinit
        static Nested()
        {
        }

        internal static readonly Singleton instance = new Singleton();
    }
}

 

 * 
 * Implementation:

Let us first discuss the code of first case Singleton which is like:

class Singleton
{
private static Singleton instance;
private static int numOfReference;
private string code;
private Singleton()
{
numOfReference = 0;
code = "Maasoom Faraz";
}
public static Singleton GetInstance()
{
if(instance == null)
{
instance = new Singleton();
}
numOfReference++;
return instance;
}
public static int Reference
{
get { return numOfReference; }
}
public string Code
{
get { return code; }
set { code = value;}
}
}

There are 3 fields in the Singleton class. Static field instance is a reference to shared object in this Singleton class. Static field numOfReference is an integer variable to hold the number of current references to the single shared object of Singleton class. Code is a string value, it is used to demonstrate that the object is shared b/w references and change made to this field through one reference can be realized through other reference.

The constructor is made private and used to initialize the numOfReference and default value of code. GetInstance() method checks the instance, if it is null then it assign it an instance of Singleton otherwise return the old reference. GetInstance() also increments the numOfReference each time it is called to keep track of current number of reference to our shared instance. Property Reference returns the number of reference referencing our shared object while Code property can be used to set/get the code string of shared object.

The second case Singleton class (Singleton2) looks like:

class Singleton2
{
private static int numOfInstance = 0;
public Singleton2()
{
if(numOfInstance == 0)
{
Console.WriteLine("\r\nCreating First Object of Singleton2 class...");
numOfInstance++;
}
else
{
throw new Exception("This class is Singleton,
+ so only one object of it can be instantiated.");
}
}
}

Here we make the constructor public and use a private field numOfInstance which is incremented for each constructor call. If numOfInstance is zero (no object is yet instantiated), a new object is allowed to made. But, if this value is not zero (there is already an object of Singleton2 class, an exception is thrown.

Now let us try these with Main() method, the code in main is like:

static void Main(string[] args)
{
Singleton obj1 = Singleton.GetInstance();
obj1.Code = "Faraz Rasheed";
Console.WriteLine("No. of references : " + Singleton.Reference);
Console.WriteLine("First Objects code: " + obj1.Code);
Singleton obj2 = Singleton.GetInstance();
Console.WriteLine("No. of references : " + Singleton.Reference);
Console.WriteLine("Second Objects code: " + obj2.Code);
Singleton2 obj3 = new Singleton2();
Singleton2 obj4 = new Singleton2();
}

First we called GetInstance() and took the reference to the new object in obj1. The code for this object is changed to Faraz Rasheed which was Maasoom Faraz by default. Then, we check for number of references to this object and code set for this. Next, we get another reference to this object through GetInstance() in obj2. We again check the number of references and code using this reference. If both the references are pointing to the same shared object, the number of references now should be 2 while the code should also be Faraz Rasheed (remember if obj2 points to a new object then code would have been Maasoom Faraz, the default one) which is shown in the output.

We also made an object of Singleton2. When constructor is called for the first reference (obj3), the new object is instantiated printing the message on Console. But when another instance of Singleton2 is attempted to be made through obj4, an exception is thrown saying Singleton class can only be instantiated once.

Sample output from the program is:

No. of references : 1
First Objects code: Faraz Rasheed 
No. of references : 2
Second Objects code: Faraz Rasheed
  
Creating First Object of Singleton2 class...

Unhandled Exception: System.Exception: This class is Singleton,so only one object of it can be instantiated. 
at Singleton.Singleton2..ctor() in c:\documents and settings\msff\my documents\visual studio projects\singleton\class1.cs:line 75 
at Singleton.Client.Main(String[] args) in c:\documents and settings\msff\my 
documents\visual studio projects\singleton\class1.cs:line 20 

Press any key to continue  

The complete source code is also attached with the article. Take a look at it to understand it to full.  

Benefits Achieved Through This Pattern: 

By using Singleton Design Pattern, it is possible for a programmer to control the instantiation of class by allowing only single object to be instantiated when desirable.

 * 
 * http://www.dotnetperls.com/singleton-static
 * http://en.csharp-online.net/Singleton_design_pattern
 * http://en.csharp-online.net/Singleton_design_pattern%3A_Thread-safe_Singleton
 * http://www.dofactory.com/Patterns/PatternSingleton.aspx
 * http://www.codeproject.com/KB/cs/genericsingleton.aspx
 */
